﻿using System;
using System.Activities;
using System.Activities.DurableInstancing;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Runtime.DurableInstancing;
using YasharEl.Infrastructure.Diagnostics.Logging;
using YasharEl.Infrastructure.ObjectFactory;
using YasharEl.Infrastructure.WorkflowModel.Configuration;
using YasharEl.Infrastructure.WorkflowModel.Diagnostics;
using YasharEl.Infrastructure.WorkflowModel.Management;
using YasharEl.Infrastructure.WorkflowModel.Persistence.Extensions;

namespace YasharEl.Infrastructure.WorkflowModel
{
    public static class WorkflowApplicationExtensions
    {
        public static WorkflowApplication SetupDefaults(this WorkflowApplication workflowApplication, IDependencyResolver resolver)
        {
            Contract.Requires<ArgumentNullException>(workflowApplication != null, "workflowApplication could not be null.");
            Contract.Requires<ArgumentNullException>(resolver != null, "resolver could not be null.");

            #region Completed

            workflowApplication.Completed = (workflowApplicationCompletedEventArgs) =>
            {
                ILogger<WorkflowApplication> logger = resolver.Resolve<ILogger<WorkflowApplication>>();
                IWorkflowCompletionHandler workflowCompletionHandler = null;
                //GenericValuePromotionPersistenceExtension genericValuePromotionPersistenceExtension = null;
                //IDictionary<string, object> promotedValues = new Dictionary<string, object>();

                logger.Debug("Workflow {0} completing...", workflowApplicationCompletedEventArgs.InstanceId);

                if (resolver.Resolve<IDependencyRegistrar>().IsTypeRegistered<IWorkflowCompletionHandler>())
                {
                    try
                    {
                        workflowCompletionHandler = resolver.Resolve<IWorkflowCompletionHandler>();
                    }
                    catch (Exception ex)
                    {
                        logger.Error(ex, "IWorkflowCompletionHandler registered in workflow model could not be resolved. Error {0}.", ex);
                    }
                }

                //IEnumerable<GenericValuePromotionPersistenceExtension> promotionPersistenceExtensions = workflowApplicationCompletedEventArgs.GetInstanceExtensions<GenericValuePromotionPersistenceExtension>();
                //List<GenericValuePromotionPersistenceExtension> promotionPersistenceExtensionsList = new List<GenericValuePromotionPersistenceExtension>(promotionPersistenceExtensions);

                //if (promotionPersistenceExtensionsList != null && promotionPersistenceExtensionsList.Count == 1)
                //{
                //    genericValuePromotionPersistenceExtension = promotionPersistenceExtensionsList[0];
                //    if (genericValuePromotionPersistenceExtension.PromotedProperties != null &&
                //        genericValuePromotionPersistenceExtension.PromotedProperties.Count > 0)
                //    {
                //        foreach (var item in genericValuePromotionPersistenceExtension.PromotedProperties)
                //        {
                //            promotedValues.Add(item.Key.ToString(), item.Value);
                //        }
                //    }
                //}

                switch (workflowApplicationCompletedEventArgs.CompletionState)
                {
                    case ActivityInstanceState.Canceled:
                        logger.Debug("Workflow {0} canceled.", workflowApplicationCompletedEventArgs.InstanceId);
                        if (workflowCompletionHandler != null)
                        {
                            workflowCompletionHandler.OnCancelled(
                                workflowApplicationCompletedEventArgs.InstanceId);
                        }
                        break;
                    case ActivityInstanceState.Closed:
                        logger.Debug("Workflow {0} closed.", workflowApplicationCompletedEventArgs.InstanceId);
                        if (workflowCompletionHandler != null)
                        {
                            workflowCompletionHandler.OnSuccessfulCompleted(
                                workflowApplicationCompletedEventArgs.InstanceId);
                        }
                        break;
                    case ActivityInstanceState.Executing:
                        logger.Debug("Workflow {0} completed with status 'Executing'.", workflowApplicationCompletedEventArgs.InstanceId);
                        break;
                    case ActivityInstanceState.Faulted:
                        logger.Error("Workflow {0} faulted.Error: {1}.", workflowApplicationCompletedEventArgs.InstanceId, workflowApplicationCompletedEventArgs.TerminationException);
                        if (workflowCompletionHandler != null)
                        {
                            workflowCompletionHandler.OnFaulted(
                                workflowApplicationCompletedEventArgs.InstanceId, workflowApplicationCompletedEventArgs.TerminationException);
                        }
                        break;
                    default:
                        break;
                }
            };

            #endregion

            #region PersistableIdle

            workflowApplication.PersistableIdle = (workflowApplicationIdleEventArgs) =>
            {
                resolver.Resolve<ILogger<WorkflowApplication>>().Debug("Workflow {0} in persistable idle state...", workflowApplicationIdleEventArgs.InstanceId);

                // returning IdleAction.Unload instructs the WorkflowApplication 
                // to persists application state and remove it from memory  
                return PersistableIdleAction.Unload;
            };

            #endregion

            #region OnUnhandledException

            workflowApplication.OnUnhandledException = (workflowApplicationUnhandledExceptionEventArgs) =>
            {
                ILogger<WorkflowApplication> logger = resolver.Resolve<ILogger<WorkflowApplication>>();

                logger.Error("Workflow {0} unhandled exception occurred : error {1}.",
                    workflowApplicationUnhandledExceptionEventArgs.InstanceId,
                    workflowApplicationUnhandledExceptionEventArgs.UnhandledException);

                try
                {
                    //TODO : Implement Workflow UnhandledException Action
                    //if (WorkflowsConfig.Instance != null)
                    //{
                    //    switch (WorkflowsConfig.Instance.UnhandledExceptionBehavior.Action)
                    //    {
                    //        case WorkflowUnhandledExceptionAction.Abandon:
                    //            resolver.Resolve<ILogger<WorkflowApplication>>().Debug("Workflow {0} abandon strategy choosen.Aborts the workflow instance in memory without touching the persisted instance state( that is, roll back to the last persist point).", workflowApplicationUnhandledExceptionEventArgs.InstanceId);
                    //            return UnhandledExceptionAction.Abort;
                    //        case WorkflowUnhandledExceptionAction.AbandonAndSuspend:
                    //            resolver.Resolve<ILogger<WorkflowApplication>>().Debug("Workflow {0} abandon strategy choosen.Aborts the workflow instance in memory without touching the persisted instance state( that is, roll back to the last persist point).", workflowApplicationUnhandledExceptionEventArgs.InstanceId);
                    //            return UnhandledExceptionAction.Abort;
                    //        case WorkflowUnhandledExceptionAction.Cancel:
                    //            resolver.Resolve<ILogger<WorkflowApplication>>().Debug("Workflow {0} cancel strategy choosen.Calls cancellation handlers for the instance and then completes the instance in memory, which may also remove it from the instance store.", workflowApplicationUnhandledExceptionEventArgs.InstanceId);
                    //            return UnhandledExceptionAction.Cancel;
                    //        case WorkflowUnhandledExceptionAction.Terminate:
                    //            resolver.Resolve<ILogger<WorkflowApplication>>().Debug("Workflow {0} terminate strategy choosen.Completes the instance in memory and removes it from the instance store.", workflowApplicationUnhandledExceptionEventArgs.InstanceId);
                    //            return UnhandledExceptionAction.Terminate;
                    //        default:
                    //            throw new NotSupportedException(string.Format("{0} action not supported.", WorkflowsConfig.Instance.UnhandledExceptionBehavior.Action));
                    //    }
                    //}
                }
                catch (Exception e)
                {
                    resolver.Resolve<ILogger<WorkflowApplication>>().Error("Exception occurred during unhandled exception processing.", e);
                }

                return UnhandledExceptionAction.Terminate;
            };

            #endregion

            #region Aborted

            workflowApplication.Aborted = (workflowApplicationAbortedEventArgs) =>
            {
                ILogger<WorkflowApplication> logger = resolver.Resolve<ILogger<WorkflowApplication>>();
                
                logger.Debug("Workflow {0} aborted with reason {1}...", workflowApplicationAbortedEventArgs.InstanceId, workflowApplicationAbortedEventArgs.Reason);

                IWorkflowCompletionHandler workflowCompletionHandler = null;
                WorkflowApplication localWorkflowApplication = workflowApplication;
                resolver.Resolve<IWorkflowOwnershipManagementService>()
                    .ReleaseOwnership(localWorkflowApplication);

                //GenericValuePromotionPersistenceExtension genericValuePromotionPersistenceExtension = null;
                //IDictionary<string, object> promotedValues = new Dictionary<string, object>();

                if (resolver.Resolve<IDependencyRegistrar>().IsTypeRegistered<IWorkflowCompletionHandler>())
                {
                    try
                    {
                        workflowCompletionHandler = resolver.Resolve<IWorkflowCompletionHandler>();
                    }
                    catch (Exception ex)
                    {
                        logger.Error(ex, "IWorkflowCompletionHandler registered in workflow model could not be resolved. Error {0}.", ex);
                    }
                }

                if (workflowCompletionHandler != null)
                {
                    workflowCompletionHandler.OnAborted(
                        workflowApplicationAbortedEventArgs.InstanceId, workflowApplicationAbortedEventArgs.Reason);
                }
            };

            #endregion

            #region Idle

            workflowApplication.Idle = (workflowApplicationIdleEventArgs) =>
            {
                resolver.Resolve<ILogger<WorkflowApplication>>().Debug("Workflow {0} in idle state...", workflowApplicationIdleEventArgs.InstanceId);
            };

            #endregion

            #region Unloaded

            workflowApplication.Unloaded = (workflowApplicationEventArg) =>
            {
                resolver.Resolve<ILogger<WorkflowApplication>>().Debug("Workflow {0} unloaded...", workflowApplicationEventArg.InstanceId);

                WorkflowApplication localWorkflowApplication = workflowApplication;
                resolver.Resolve<IWorkflowOwnershipManagementService>()
                    .ReleaseOwnership(localWorkflowApplication);
            };

            #endregion

            workflowApplication.Extensions.Add(new LoggingTrackingParticipant(resolver.Resolve<ILogger<LoggingTrackingParticipant>>()));

            return workflowApplication;
        }

        public static WorkflowApplication SetupInstanceStore(this WorkflowApplication workflowApplication
            , IDependencyResolver resolver)
        {
            Contract.Requires<ArgumentNullException>(workflowApplication != null, "workflowApplication could not be null.");
            Contract.Requires<ArgumentNullException>(resolver != null, "resolver could not be null.");

            ILogger<WorkflowApplication> logger = resolver.Resolve<ILogger<WorkflowApplication>>();

            logger.Debug("Configuring workflow application instance store...");

            resolver.Resolve<IWorkflowOwnershipManagementService>()
                .TakeOwnership(workflowApplication);

            logger.Debug("Workflow application instance store successfully configured...");

            return workflowApplication;
        }
    }
}
